<%- # the license inside this block applies to this file
# Copyright 2017 Google Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-%>
<%= lines(autogen_notice(:go, pwd)) -%>

package google

import (
<%- # We list all the v2 imports here, because we run 'goimports' to guess the correct
    # set of imports, which will never guess the major version correctly. -%>
  "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
  "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
  "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
  "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
  "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
  "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure"
  "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
  "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
  "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging"
)

<%= lines(compile(pwd + '/' + object.custom_code.constants)) if object.custom_code.constants -%>

<%
    resource_name = product_ns + object.name
    properties = object.all_user_properties
    update_body_properties = properties_without_custom_update(object.settable_properties)
    update_body_properties = update_body_properties.reject(&:input) if object.update_verb == :PATCH
    # Handwritten TF Operation objects will be shaped like accessContextManager while the Google Go Client will have a name like accesscontextmanager
    client_name = @config.client_name || product_ns
    client_name_camel = client_name.camelize(:lower)
    client_name_pascal = client_name.camelize(:upper)
    client_name_lower = client_name.downcase
    has_project = object.base_url.include?('{{project}}') || (object.create_url && object.create_url.include?('{{project}}'))
    has_region = object.base_url.include?('{{region}}') && object.parameters.any?{ |p| p.name == 'region' && p.ignore_read }
    # In order of preference, use TF override,
    # general defined timeouts, or default Timeouts
    timeouts = object.timeouts
    timeouts ||= object&.async&.operation&.timeouts
    timeouts ||= Api::Timeouts.new
-%>

<%  if object.async&.is_a? Api::OpAsync -%>
    <%  if object.async.include_project -%>
var project string
    <%  end -%>
<% end -%>

func resource<%= resource_name -%>() *schema.Resource {
    return &schema.Resource{
        Create: resource<%= resource_name -%>Create,
        Read: resource<%= resource_name -%>Read,
<%      if updatable?(object, properties) -%>
        Update: resource<%= resource_name -%>Update,
<%      end -%>
        Delete: resource<%= resource_name -%>Delete,
<%      if object.settable_properties.any? {|p| p.unordered_list} && !object.custom_code.resource_definition -%>
        CustomizeDiff: customdiff.All(
<%=
            object.settable_properties.select { |p| p.unordered_list }
                               .map { |p| "resource#{resource_name}#{p.name.camelize(:upper)}SetStyleDiff"}
                               .join(",\n")
-%>
        ),
<%      end -%>

<%      unless object.exclude_import -%>
        Importer: &schema.ResourceImporter{
            State: resource<%= resource_name -%>Import,
        },
<%      end -%>

        Timeouts: &schema.ResourceTimeout {
            Create: schema.DefaultTimeout(<%= timeouts.insert_minutes -%> * time.Minute),
<%      if updatable?(object, properties) -%>
            Update: schema.DefaultTimeout(<%= timeouts.update_minutes -%> * time.Minute),
<%      end -%>
            Delete: schema.DefaultTimeout(<%= timeouts.delete_minutes -%> * time.Minute),
        },

<%      if object.schema_version -%>
        SchemaVersion: <%= object.schema_version -%>,
        StateUpgraders: []schema.StateUpgrader{
<%        for v in 0..object.schema_version-1 -%>
            {
                Type: resource<%= "#{resource_name}ResourceV#{v}" -%>().CoreConfigSchema().ImpliedType(),
                Upgrade: resource<%= "#{resource_name}UpgradeV#{v}" -%>,
                Version: <%= v -%>,
            },
        },
<%        end -%>
<%      end -%>

<%=     lines(compile(pwd + '/' + object.custom_code.resource_definition)) if object.custom_code.resource_definition -%>

        Schema: map[string]*schema.Schema{
<%      order_properties(properties).each do |prop| -%>
<%=       lines(build_schema_property(prop, object, pwd)) -%>
<%      end -%>
<%-     unless object.virtual_fields.empty? -%>
<%-       object.virtual_fields.each do |field| -%>
            "<%= field.name -%>": {
              Type: <%= tf_type(field) -%>,
              Optional: true,
              Default:  <%= go_literal(field.default_value) -%>,
            },
<%        end -%>
<%      end -%>
<%=     lines(compile(pwd + '/' + object.custom_code.extra_schema_entry)) if object.custom_code.extra_schema_entry -%>
<%      if has_project -%>
            "project": {
                Type:     schema.TypeString,
                Optional: true,
                Computed: true,
                ForceNew: true,
            },
<%      end -%>
<%      if object.has_self_link -%>
            "self_link": {
                Type:     schema.TypeString,
                Computed: true,
            },
<%      end -%>
        },
        UseJSONNumber: true,
    }
}

<% properties.each do |prop| -%>
<%= lines(build_subresource_schema(prop, object, pwd), 1) -%>
<% end -%>

<% object.settable_properties.select {|p| p.unordered_list}.each do |prop| -%>
func resource<%= resource_name -%><%= prop.name.camelize(:upper) -%>SetStyleDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error {
<%=
    compile_template(pwd + '/templates/terraform/unordered_list_customize_diff.erb',
                     prop: prop,
                     resource_name: resource_name)
-%>
}
<% end -%>

func resource<%= resource_name -%>Create(d *schema.ResourceData, meta interface{}) error {
    config := meta.(*Config)
<%  if object.custom_code.custom_create -%>
    <%= lines(compile(pwd + '/' + object.custom_code.custom_create))  -%>
<%  else  -%>
    userAgent, err := generateUserAgentString(d, config.userAgent)
    if err != nil {
        return err
    }

    obj := make(map[string]interface{})
<%  object.settable_properties.each do |prop| -%>
    <% schemaPrefix = prop.flatten_object ? "nil" : "d.Get( \"#{prop.name.underscore}\" )" -%>
    <%= prop.api_name -%>Prop, err := expand<%= "Nested" if object.nested_query -%><%= resource_name -%><%= titlelize_property(prop) -%>(<%= schemaPrefix -%>, d, config)
    if err != nil {
        return err
<%    if prop.send_empty_value -%>
    } else if v, ok := d.GetOkExists("<%= prop.name.underscore -%>"); ok || !reflect.DeepEqual(v, <%= prop.api_name -%>Prop) {
<%    elsif prop.flatten_object -%>
    } else if !isEmptyValue(reflect.ValueOf(<%= prop.api_name -%>Prop)) {
<%    else -%>
    } else if v, ok := d.GetOkExists("<%= prop.name.underscore -%>"); !isEmptyValue(reflect.ValueOf(<%= prop.api_name -%>Prop)) && (ok || !reflect.DeepEqual(v, <%= prop.api_name -%>Prop)) {
<%    end -%>
        obj["<%= prop.api_name -%>"] = <%= prop.api_name -%>Prop
    }
<%  end -%>

<%  if object.custom_code.encoder -%>
    obj, err = resource<%= resource_name -%>Encoder(d, meta, obj)
    if err != nil {
        return err
    }
<%  end -%>

<%  if object.mutex -%>
    lockName, err := replaceVars(d, config, "<%= object.mutex -%>")
    if err != nil {
        return err
    }
    mutexKV.Lock(lockName)
    defer mutexKV.Unlock(lockName)
<%  end -%>

    url, err := replaceVars(d, config, "<%= "{{#{object.__product.name}BasePath}}#{object.create_uri}" -%>")
    if err != nil {
        return err
    }

    log.Printf("[DEBUG] Creating new <%= object.name -%>: %#v", obj)
<%  if object.nested_query&.modify_by_patch -%>
<%# Keep this after mutex - patch request data relies on current resource state %>
    obj, err = resource<%= resource_name -%>PatchCreateEncoder(d, meta, obj)
    if err != nil {
        return err
    }
<%    if object.update_mask -%>
    url, err = addQueryParams(url, map[string]string{"updateMask": "<%= object.nested_query.keys.join('.') -%>"})
    if err != nil {
        return err
    }
<%    end -%>
<%  end -%>
    billingProject := ""

<%  if has_project -%>
    project, err := getProject(d, config)
    if err != nil {
        return fmt.Errorf("Error fetching project for <%= object.name -%>: %s", err)
    }
    billingProject = project
<%  end -%>

<%  if object.supports_indirect_user_project_override -%>
    if parts := regexp.MustCompile(`projects\/([^\/]+)\/`).FindStringSubmatch(url); parts != nil {
        billingProject = parts[1]
    }
<%  end -%>

    // err == nil indicates that the billing_project value was found
    if bp, err := getBillingProject(d, config); err == nil {
      billingProject = bp
    }

<%= lines(compile(pwd + '/' + object.custom_code.pre_create)) if object.custom_code.pre_create -%>
    res, err := sendRequestWithTimeout(config, "<%= object.create_verb.to_s.upcase -%>", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutCreate)<%= object.error_retry_predicates ? ", " + object.error_retry_predicates.join(',') : "" -%>)
    if err != nil {
<%  if object.custom_code.post_create_failure && object.async.nil? # Only add if not handled by async error handling -%>
        resource<%= resource_name -%>PostCreateFailure(d, meta)
<%  end -%>
        return fmt.Errorf("Error creating <%= object.name -%>: %s", err)
    }
<% # Set resource properties from create API response (unless it returns an Operation) -%>
<%  unless object.async&.is_a? Api::OpAsync -%>
    <% object.gettable_properties.each do |prop| -%>
        <% if object.identity.include?(prop) && prop.output -%>
    if err := d.Set("<%= prop.name.underscore -%>", flatten<%= "Nested" if object.nested_query -%><%= resource_name -%><%= titlelize_property(prop) -%>(res["<%= prop.api_name -%>"], d, config)); err != nil {
        return fmt.Errorf(`Error setting computed identity field "<%=prop.name.underscore%>": %s`, err)
    }
        <% end -%>
    <% end -%>
<% end -%>

    // Store the ID now
    id, err := replaceVars(d, config, "<%= id_format(object) -%>")
    if err != nil {
        return fmt.Errorf("Error constructing id: %s", err)
    }
    d.SetId(id)

<%  if object.async&.allow?('create') -%>
<%    if object.async.is_a? Api::OpAsync -%>
<%      if object.async.result.resource_inside_response and not object.identity.empty? -%>
    // Use the resource in the operation response to populate
    // identity fields and d.Id() before read
    var opRes map[string]interface{}
    err = <%= client_name_camel -%>OperationWaitTimeWithResponse(
    config, res, &opRes, <% if has_project || object.async.include_project -%> project, <% end -%> "Creating <%= object.name -%>", userAgent,
        d.Timeout(schema.TimeoutCreate))
    if err != nil {
<%        if object.custom_code.post_create_failure -%>
        resource<%= resource_name -%>PostCreateFailure(d, meta)
<%        end -%>
        // The resource didn't actually create
        d.SetId("")
        return fmt.Errorf("Error waiting to create <%= object.name -%>: %s", err)
    }

<%      if object.custom_code.decoder -%>
    opRes, err = resource<%= resource_name -%>Decoder(d, meta, opRes)
    if err != nil {
        return fmt.Errorf("Error decoding response from operation: %s", err)
    }
    if opRes == nil {
        return fmt.Errorf("Error decoding response from operation, could not find object")
    }
<%      end -%>

<%      if object.nested_query -%>
    if _, ok := opRes["<%=object.nested_query.keys[0]-%>"]; ok {
        opRes, err = flattenNested<%= resource_name -%>(d, meta, opRes)
        if err != nil {
            return fmt.Errorf("Error getting nested object from operation response: %s", err)
        }
        if opRes == nil {
            // Object isn't there any more - remove it from the state.
            return fmt.Errorf("Error decoding response from operation, could not find nested object")
        }
    }
<%      end -%>
    <% object.gettable_properties.each do |prop| -%>
    <% if object.identity.include?(prop) -%>
    if err := d.Set("<%= prop.name.underscore -%>", flatten<%= "Nested" if object.nested_query -%><%= resource_name -%><%= titlelize_property(prop) -%>(opRes["<%= prop.api_name -%>"], d, config)); err != nil {
        return err
    }
    <% end -%>
    <% end -%>

    // This may have caused the ID to update - update it if so.
    id, err = replaceVars(d, config, "<%= id_format(object) -%>")
    if err != nil {
        return fmt.Errorf("Error constructing id: %s", err)
    }
    d.SetId(id)

    <% else -%>
    err = <%= client_name_camel -%>OperationWaitTime(
    config, res, <% if has_project || object.async.include_project -%> project, <% end -%> "Creating <%= object.name -%>", userAgent,
        d.Timeout(schema.TimeoutCreate))

    if err != nil {
<%      if object.custom_code.post_create_failure -%>
        resource<%= resource_name -%>PostCreateFailure(d, meta)
<%      end -%>
        // The resource didn't actually create
        d.SetId("")
        return fmt.Errorf("Error waiting to create <%= object.name -%>: %s", err)
    }

<%    end -%>
<%  end -%>
<%  end -%>

<%= lines(compile(pwd + '/' + object.custom_code.post_create)) if object.custom_code.post_create -%>

<%  if object.async&.allow?('create') -%>
<%    if object.async.is_a? Provider::Terraform::PollAsync -%>
    err = PollingWaitTime(resource<%= resource_name -%>PollRead(d, meta), <%= object.async.check_response_func_existence -%>, "Creating <%= object.name -%>", d.Timeout(schema.TimeoutCreate), <%= object.async.target_occurrences -%>)
    if err != nil {
<%      if object.async.suppress_error -%>
        log.Printf("[ERROR] Unable to confirm eventually consistent <%= object.name -%> %q finished updating: %q", d.Id(), err)
<%      else -%>
<%        if object.custom_code.post_create_failure -%>
        resource<%= resource_name -%>PostCreateFailure(d, meta)
<%        end -%>
        return fmt.Errorf("Error waiting to create <%= object.name -%>: %s", err)
<%      end -%>
    }

<%    end -%>
<% end -%>

    log.Printf("[DEBUG] Finished creating <%= object.name -%> %q: %#v", d.Id(), res)

    return resource<%= resource_name -%>Read(d, meta)
<%  end # if custom_create  -%>
}

<%  if object.async&.is_a? Provider::Terraform::PollAsync -%>
func resource<%= resource_name -%>PollRead(d *schema.ResourceData, meta interface{}) PollReadFunc {
    return func() (map[string]interface{}, error) {
<%    if object.async.custom_poll_read -%>
<%= lines(compile(pwd + '/' + object.async.custom_poll_read)) -%>
<%    else -%>
        config := meta.(*Config)

        url, err := replaceVars(d, config, "<%= "{{#{object.__product.name}BasePath}}#{object.self_link_uri}" -%>")
        if err != nil {
            return nil, err
        }

        billingProject := ""

<%  if has_project -%>
        project, err := getProject(d, config)
        if err != nil {
            return nil, fmt.Errorf("Error fetching project for <%= object.name -%>: %s", err)
        }
        billingProject = project
<%  end -%>

<%  if object.supports_indirect_user_project_override -%>
        if parts := regexp.MustCompile(`projects\/([^\/]+)\/`).FindStringSubmatch(url); parts != nil {
            billingProject = parts[1]
        }
<%  end -%>

        // err == nil indicates that the billing_project value was found
        if bp, err := getBillingProject(d, config); err == nil {
        billingProject = bp
        }

        userAgent, err := generateUserAgentString(d, config.userAgent)
        if err != nil {
            return nil, err
        }

        res, err := sendRequest(config, "<%= object.read_verb.to_s.upcase -%>", billingProject, url, userAgent, nil<%= object.error_retry_predicates ? ", " + object.error_retry_predicates.join(',') : "" -%>)
        if err != nil {
            return res, err
        }
    <%  if object.nested_query -%>
        res, err = flattenNested<%= resource_name -%>(d, meta, res)
        if err != nil {
            return nil, err
        }

        if res == nil {
            // Nested object not found, spoof a 404 error for poll
            return nil, &googleapi.Error{
                Code: 404,
                Message: "nested object <%= resource_name%> not found",
            }
        }

    <%  end -%>
    <%  if object.custom_code.decoder -%>
        res, err = resource<%= resource_name -%>Decoder(d, meta, res)
        if err != nil {
            return nil, err
        }
        if res == nil {
            // Decoded object not found, spoof a 404 error for poll
            return nil, &googleapi.Error{
                Code: 404,
                Message: "could not find object <%= resource_name%>",
            }
        }

    <%  end -%>
        return res, nil
<%    end # ifelse object.async.custom_poll_read -%>
    }
}
<%  end -%>

func resource<%= resource_name -%>Read(d *schema.ResourceData, meta interface{}) error {
    config := meta.(*Config)
    userAgent, err := generateUserAgentString(d, config.userAgent)
    if err != nil {
        return err
    }

    url, err := replaceVars(d, config, "<%= "{{#{object.__product.name}BasePath}}#{object.self_link_uri}#{object.read_query_params}" -%>")
    if err != nil {
        return err
    }

    billingProject := ""

<%  if has_project -%>
    project, err := getProject(d, config)
    if err != nil {
        return fmt.Errorf("Error fetching project for <%= object.name -%>: %s", err)
    }
    billingProject = project
<%  end -%>

<%  if object.supports_indirect_user_project_override -%>
    if parts := regexp.MustCompile(`projects\/([^\/]+)\/`).FindStringSubmatch(url); parts != nil {
        billingProject = parts[1]
    }
<%  end -%>

    // err == nil indicates that the billing_project value was found
    if bp, err := getBillingProject(d, config); err == nil {
      billingProject = bp
    }

    res, err := sendRequest(config, "<%= object.read_verb.to_s.upcase -%>", billingProject, url, userAgent, nil<%= object.error_retry_predicates ? ", " + object.error_retry_predicates.join(',') : "" -%>)
    if err != nil {
<%  if object.read_error_transform -%>
        return handleNotFoundError(<%= object.read_error_transform %>(err), d, fmt.Sprintf("<%= resource_name -%> %q", d.Id()))
<%  else -%>
        return handleNotFoundError(err, d, fmt.Sprintf("<%= resource_name -%> %q", d.Id()))
<%  end -%>
    }

<%  if object.nested_query -%>
    res, err = flattenNested<%= resource_name -%>(d, meta, res)
    if err != nil {
        return err
    }

    if res == nil {
        // Object isn't there any more - remove it from the state.
        log.Printf("[DEBUG] Removing <%= resource_name -%> because it couldn't be matched.")
        d.SetId("")
        return nil
    }

<%  end -%>

<%  if object.custom_code.decoder -%>
    res, err = resource<%= resource_name -%>Decoder(d, meta, res)
    if err != nil {
        return err
    }

    if res == nil {
        // Decoding the object has resulted in it being gone. It may be marked deleted
        log.Printf("[DEBUG] Removing <%= resource_name -%> because it no longer exists.")
        d.SetId("")
        return nil
    }

<%  end -%>
<%- unless object.virtual_fields.empty? -%>
  // Explicitly set virtual fields to default values if unset
<%-   object.virtual_fields.each do |field| -%>
    if _, ok := d.GetOkExists("<%= field.name -%>"); !ok {
      if err := d.Set("<%= field.name -%>", <%= go_literal(field.default_value) -%>); err != nil {
      	return fmt.Errorf("Error setting <%= field.name -%>: %s", err)
      }
    }
<%    end -%>
<%  end -%>
<%  if has_project -%>
    if err := d.Set("project", project); err != nil {
        return fmt.Errorf("Error reading <%= object.name -%>: %s", err)
    }
<%  end -%>

<%  if has_region -%>
    region, err := getRegion(d, config)
    if err != nil {
        return err
    }
    if err := d.Set("region", region); err != nil {
        return fmt.Errorf("Error reading <%= object.name -%>: %s", err)
    }
<%  end -%>

<%  object.gettable_properties.reject{|p| p.ignore_read }.each do |prop| -%>
<%    if prop.flatten_object -%>
// Terraform must set the top level schema field, but since this object contains collapsed properties
// it's difficult to know what the top level should be. Instead we just loop over the map returned from flatten.
    if flattenedProp := flatten<%= "Nested" if object.nested_query -%><%= resource_name -%><%= titlelize_property(prop) -%>(res["<%= prop.api_name -%>"], d, config); flattenedProp != nil {
        if gerr, ok := flattenedProp.(*googleapi.Error); ok {
			return fmt.Errorf("Error reading <%= object.name -%>: %s", gerr)
		}
        casted := flattenedProp.([]interface{})[0]
        if casted != nil {
            for k, v := range casted.(map[string]interface{}) {
                if err := d.Set(k, v); err != nil {
                	return fmt.Errorf("Error setting %s: %s", k, err)
                }
            }
        }
    }
<%    else -%>
    if err := d.Set("<%= prop.name.underscore -%>", flatten<%= "Nested" if object.nested_query -%><%= resource_name -%><%= titlelize_property(prop) -%>(res["<%= prop.api_name -%>"], d, config)); err != nil {
        return fmt.Errorf("Error reading <%= object.name -%>: %s", err)
    }
<%    end -%>
<%  end -%>
<%  if object.has_self_link -%>
    if err := d.Set("self_link", ConvertSelfLinkToV1(res["selfLink"].(string))); err != nil {
        return fmt.Errorf("Error reading <%= object.name -%>: %s", err)
    }
<%  end -%>

    return nil
}

<% if updatable?(object, object.root_properties) -%>
func resource<%= resource_name -%>Update(d *schema.ResourceData, meta interface{}) error {
    config := meta.(*Config)
    userAgent, err := generateUserAgentString(d, config.userAgent)
    if err != nil {
    	return err
    }

    billingProject := ""

<%  if has_project -%>
    project, err := getProject(d, config)
    if err != nil {
        return fmt.Errorf("Error fetching project for <%= object.name -%>: %s", err)
    }
    billingProject = project
<%  end -%>


<%  if !object.input -%>
    obj := make(map[string]interface{})
<%  update_body_properties.each do |prop| -%>
    <%# flattened objects won't have something stored in state so instead nil is passed to the next expander. -%>
    <% schemaPrefix = prop.flatten_object ? "nil" : "d.Get( \"#{prop.name.underscore}\" )" -%>
    <%= prop.api_name -%>Prop, err := expand<%= "Nested" if object.nested_query -%><%= resource_name -%><%= titlelize_property(prop) -%>(<%= schemaPrefix -%>, d, config)
    if err != nil {
        return err
<%      if prop.send_empty_value -%>
    } else if v, ok := d.GetOkExists("<%= prop.name.underscore -%>"); ok || !reflect.DeepEqual(v, <%= prop.api_name -%>Prop) {
<%      elsif prop.flatten_object -%>
    } else if !isEmptyValue(reflect.ValueOf(<%= prop.api_name -%>Prop)) {
<%      else -%>
    } else if v, ok := d.GetOkExists("<%= prop.name.underscore -%>"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, <%= prop.api_name -%>Prop)) {
<%      end -%>
        obj["<%= prop.api_name -%>"] = <%= prop.api_name -%>Prop
    }
<%  end -%>

<%# We need to decide what encoder to use here - if there's an update encoder, use that! -%>
<%  if object.custom_code.update_encoder -%>
    obj, err = resource<%= resource_name -%>UpdateEncoder(d, meta, obj)
    if err != nil {
        return err
    }
<%  elsif object.custom_code.encoder -%>
    obj, err = resource<%= resource_name -%>Encoder(d, meta, obj)
    if err != nil {
        return err
    }
<%  end -%>

<%  if object.mutex -%>
    lockName, err := replaceVars(d, config, "<%= object.mutex -%>")
    if err != nil {
        return err
    }
    mutexKV.Lock(lockName)
    defer mutexKV.Unlock(lockName)
<%  end -%>

    url, err := replaceVars(d, config, "<%= "{{#{object.__product.name}BasePath}}#{update_uri(object, object.update_url)}" -%>")
    if err != nil {
        return err
    }

    log.Printf("[DEBUG] Updating <%= object.name -%> %q: %#v", d.Id(), obj)
<%= lines(compile(pwd + '/templates/terraform/update_mask.erb')) if object.update_mask -%>
<%= lines(compile(pwd + '/' + object.custom_code.pre_update)) if object.custom_code.pre_update -%>
<%  if object.nested_query&.modify_by_patch -%>
<%# Keep this after mutex - patch request data relies on current resource state %>
    obj, err = resource<%= resource_name -%>PatchUpdateEncoder(d, meta, obj)
    if err != nil {
        return err
    }
<%  end -%>
<%  if object.supports_indirect_user_project_override -%>
     if parts := regexp.MustCompile(`projects\/([^\/]+)\/`).FindStringSubmatch(url); parts != nil {
        billingProject = parts[1]
    }
<%  end -%>

    // err == nil indicates that the billing_project value was found
    if bp, err := getBillingProject(d, config); err == nil {
      billingProject = bp
    }

<% if object.update_mask && field_specific_update_methods(object.root_properties) -%>
// if updateMask is empty we are not updating anything so skip the post
if len(updateMask) > 0 {
<% end -%>
    res, err := sendRequestWithTimeout(config, "<%= object.update_verb -%>", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutUpdate)<%= object.error_retry_predicates ? ", " + object.error_retry_predicates.join(',') : "" -%>)

    if err != nil {
        return fmt.Errorf("Error updating <%= object.name -%> %q: %s", d.Id(), err)
    } else {
	log.Printf("[DEBUG] Finished updating <%= object.name -%> %q: %#v", d.Id(), res)
    }

<%  if object.async&.allow?('update') -%>
<%    if object.async.is_a? Api::OpAsync -%>
    err = <%= client_name_camel -%>OperationWaitTime(
        config, res, <% if has_project || object.async.include_project -%> project, <% end -%> "Updating <%= object.name -%>", userAgent,
        d.Timeout(schema.TimeoutUpdate))

    if err != nil {
        return err
    }
<%    elsif object.async.is_a? Provider::Terraform::PollAsync -%>
    err = PollingWaitTime(resource<%= resource_name -%>PollRead(d, meta), <%= object.async.check_response_func_existence -%>, "Updating <%= object.name -%>", d.Timeout(schema.TimeoutUpdate), <%= object.async.target_occurrences -%>)
    if err != nil {
<%      if object.async.suppress_error-%>
        log.Printf("[ERROR] Unable to confirm eventually consistent <%= object.name -%> %q finished updating: %q", d.Id(), err)
<%      else -%>
        return err
<%      end -%>
    }
<%    end -%>
<%  end -%>
<% if object.update_mask && field_specific_update_methods(object.root_properties) -%>
  }
<% end -%>
<%  end # if !object.input -%>
<%    if field_specific_update_methods(object.root_properties) -%>
    d.Partial(true)

<% properties_by_custom_update(object.root_properties)
    .sort_by {|k, _| k.nil? ? "" : k[:update_id].to_s}
    .each do |key, props|
-%>
if <%= props.map { |prop| "d.HasChange(\"#{prop.name.underscore}\")" }.join ' || ' -%> {
        obj := make(map[string]interface{})

<%-      unless key[:fingerprint_name] == nil -%>
        getUrl, err := replaceVars(d, config, "<%= "{{#{object.__product.name}BasePath}}#{object.self_link_uri}" -%>")
        if err != nil {
            return err
        }
<%         if object.supports_indirect_user_project_override -%>
        if parts := regexp.MustCompile(`projects\/([^\/]+)\/`).FindStringSubmatch(url); parts != nil {
            billingProject = parts[1]
        }
<%         end -%>

        // err == nil indicates that the billing_project value was found
        if bp, err := getBillingProject(d, config); err == nil {
        billingProject = bp
        }

        getRes, err := sendRequest(config, "<%= object.read_verb.to_s.upcase -%>", billingProject, getUrl, userAgent, nil<%= object.error_retry_predicates ? ", " + object.error_retry_predicates.join(',') : "" -%>)
        if err != nil {
            return handleNotFoundError(err, d, fmt.Sprintf("<%= resource_name -%> %q", d.Id()))
        }

        obj["<%= key[:fingerprint_name] %>"] = getRes["<%= key[:fingerprint_name] %>"]

<%       end # unless key[:fingerprint_name] -%>
<%      custom_update_properties_by_key(properties, key)
          .reject(&:url_param_only)
          .each do |prop| -%>
        <% schemaPrefix = prop.flatten_object ? "nil" : "d.Get( \"#{prop.name.underscore}\" )" -%>
        <%= prop.api_name -%>Prop, err := expand<%= "Nested" if object.nested_query -%><%= resource_name -%><%= titlelize_property(prop) -%>(<%= schemaPrefix -%>, d, config)
        if err != nil {
            return err
<%#         There is some nuance in when we choose to send a value to an update function.
            This is unfortunate, but it's because of the way that GCP works, so there's
            no easy way out.  Some APIs require you to send `enable_foo: false`, while
            others will crash if you send `attribute: ''`.  We require this nuance to
            be annotated in api.yaml, since it is not discoverable automatically.

            The behavior here, which we believe to be correct, is to send a value if
            * It is non-empty OR
            * It is marked send_empty_value in api.yaml.
            AND
            * It has been set by the user OR
            * It has been modified by the expander in any way

            This subsumes both `ForceSendFields` and `NullFields` in the go API client -
            `NullFields` is a special case of `send_empty_value` where the empty value
            in question is go's literal nil.
-%>
<%          if prop.send_empty_value -%>
        } else if v, ok := d.GetOkExists("<%= prop.name.underscore -%>"); ok || !reflect.DeepEqual(v, <%= prop.api_name -%>Prop) {
<%          elsif prop.flatten_object -%>
        } else if !isEmptyValue(reflect.ValueOf(<%= prop.api_name -%>Prop)) {
<%          else -%>
        } else if v, ok := d.GetOkExists("<%= prop.name.underscore -%>"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, <%= prop.api_name -%>Prop)) {
<%          end -%>
            obj["<%= prop.api_name -%>"] = <%= prop.api_name -%>Prop
        }
<%    end # props.each -%>

<%# We need to decide what encoder to use here - if there's an update encoder, use that! -%>
<%  if object.custom_code.update_encoder -%>
    obj, err = resource<%= resource_name -%>UpdateEncoder(d, meta, obj)
    if err != nil {
        return err
    }
<%  end -%>

<%      if object.mutex -%>
        lockName, err := replaceVars(d, config, "<%= object.mutex -%>")
        if err != nil {
            return err
        }
        mutexKV.Lock(lockName)
        defer mutexKV.Unlock(lockName)
<%      end -%>

        url, err := replaceVars(d, config, "<%= "{{#{object.__product.name}BasePath}}#{update_uri(object, key[:update_url])}" -%>")
        if err != nil {
            return err
        }
<%      if object.supports_indirect_user_project_override -%>
        if parts := regexp.MustCompile(`projects\/([^\/]+)\/`).FindStringSubmatch(url); parts != nil {
            billingProject = parts[1]
        }
<%      end -%>

        // err == nil indicates that the billing_project value was found
        if bp, err := getBillingProject(d, config); err == nil {
        billingProject = bp
        }

        res, err := sendRequestWithTimeout(config, "<%= key[:update_verb] -%>", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutUpdate)<%= object.error_retry_predicates ? ", " + object.error_retry_predicates.join(',') : "" -%>)
        if err != nil {
            return fmt.Errorf("Error updating <%= object.name -%> %q: %s", d.Id(), err)
        } else {
	    log.Printf("[DEBUG] Finished updating <%= object.name -%> %q: %#v", d.Id(), res)
	}

<%      if object.async&.allow?('update') -%>
<%          if object.async.is_a? Api::OpAsync-%>
        err = <%= client_name_camel -%>OperationWaitTime(
            config, res, <% if has_project || object.async.include_project -%> project, <% end -%> "Updating <%= object.name -%>", userAgent,
            d.Timeout(schema.TimeoutUpdate))
        if err != nil {
            return err
        }
<%          elsif object.async.is_a? Provider::Terraform::PollAsync -%>
        err = PollingWaitTime(resource<%= resource_name -%>PollRead(d, meta), <%= object.async.check_response_func_existence -%>, "Updating <%= object.name -%>", d.Timeout(schema.TimeoutUpdate), <%= object.async.target_occurrences -%>)
        if err != nil {
<%              if object.async.suppress_error-%>
        log.Printf("[ERROR] Unable to confirm eventually consistent <%= object.name -%> %q finished updating: %q", d.Id(), err)
<%              else -%>
        return err
<%              end -%>
        }
<%          end -%>
<%      end -%>
    }
<%  end -%>

  d.Partial(false)
<% end -%>

<%= lines(compile(pwd + '/' + object.custom_code.post_update)) if object.custom_code.post_update -%>
    return resource<%= resource_name -%>Read(d, meta)
}
<% end # if updatable? -%>

func resource<%= resource_name -%>Delete(d *schema.ResourceData, meta interface{}) error {
<% if object.skip_delete -%>
    log.Printf("[WARNING] <%= object.__product.name + " " + object.name %> resources" +
    " cannot be deleted from Google Cloud. The resource %s will be removed from Terraform" +
    " state, but will still be present on Google Cloud.", d.Id())
    d.SetId("")

    return nil
<% else -%>
    config := meta.(*Config)
    userAgent, err := generateUserAgentString(d, config.userAgent)
    if err != nil {
    	return err
    }

<% if object.custom_code.custom_delete -%>
<%= lines(compile(pwd + '/' + object.custom_code.custom_delete)) -%>
<% else -%>

    billingProject := ""

<%  if has_project -%>
    project, err := getProject(d, config)
    if err != nil {
        return fmt.Errorf("Error fetching project for <%= object.name -%>: %s", err)
    }
    billingProject = project
<%  end -%>

<%  if object.mutex -%>
    lockName, err := replaceVars(d, config, "<%= object.mutex -%>")
    if err != nil {
        return err
    }
    mutexKV.Lock(lockName)
    defer mutexKV.Unlock(lockName)
<%  end -%>

    url, err := replaceVars(d, config, "<%= "{{#{object.__product.name}BasePath}}#{object.delete_uri}" -%>")
    if err != nil {
        return err
    }

<%# If the deletion of the object requires sending a request body, the custom code will set 'obj' -%>
    var obj map[string]interface{}
<%= lines(compile(pwd + '/' + object.custom_code.pre_delete)) if object.custom_code.pre_delete -%>
<%  if object.nested_query&.modify_by_patch -%>
<%# Keep this after mutex - patch request data relies on current resource state %>
    obj, err = resource<%= resource_name -%>PatchDeleteEncoder(d, meta, obj)
    if err != nil {
        return handleNotFoundError(err, d, "<%= object.name -%>")
    }
<%    if object.update_mask -%>
    url, err = addQueryParams(url, map[string]string{"updateMask": "<%= object.nested_query.keys.join('.') -%>"})
    if err != nil {
        return err
    }
<%    end -%>
<%  end -%>
<%  if object.supports_indirect_user_project_override -%>

    if parts := regexp.MustCompile(`projects\/([^\/]+)\/`).FindStringSubmatch(url); parts != nil {
        billingProject = parts[1]
    }

<%  end -%>
    log.Printf("[DEBUG] Deleting <%= object.name -%> %q", d.Id())

    // err == nil indicates that the billing_project value was found
    if bp, err := getBillingProject(d, config); err == nil {
      billingProject = bp
    }

    res, err := sendRequestWithTimeout(config, "<%= object.delete_verb.to_s.upcase -%>", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutDelete)<%= object.error_retry_predicates ? ", " + object.error_retry_predicates.join(',') : "" -%>)
    if err != nil {
        return handleNotFoundError(err, d, "<%= object.name -%>")
    }

<%  if object.async&.allow?('delete') -%>
<%    if object.async.is_a? Provider::Terraform::PollAsync -%>
    err = PollingWaitTime(resource<%= resource_name -%>PollRead(d, meta), <%= object.async.check_response_func_absence -%>, "Deleting <%= object.name -%>", d.Timeout(schema.TimeoutCreate), <%= object.async.target_occurrences -%>)
    if err != nil {
<%      if object.async.suppress_error -%>
        log.Printf("[ERROR] Unable to confirm eventually consistent <%= object.name -%> %q finished updating: %q", d.Id(), err)
<%      else -%>
        return fmt.Errorf("Error waiting to delete <%= object.name -%>: %s", err)
<%      end -%>
    }
<%    else -%>
    err = <%= client_name_camel -%>OperationWaitTime(
        config, res, <% if has_project || object.async.include_project-%> project, <% end -%> "Deleting <%= object.name -%>", userAgent,
        d.Timeout(schema.TimeoutDelete))

    if err != nil {
        return err
    }
<%    end -%>
<%  end -%>
<%= lines(compile(pwd + '/' + object.custom_code.post_delete)) if object.custom_code.post_delete -%>

    log.Printf("[DEBUG] Finished deleting <%= object.name -%> %q: %#v", d.Id(), res)
    return nil
<% end # custom code -%>
<% end # skip_delete -%>
}

<% unless object.exclude_import -%>
func resource<%= resource_name -%>Import(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
<% if object.custom_code.custom_import -%>
<%= lines(compile(pwd + '/' + object.custom_code.custom_import)) -%>
<% else -%>
    config := meta.(*Config)
    if err := parseImportId([]string{
<%   for import_id in import_id_formats_from_resource(object) -%>
        "<%= format2regex(import_id) %>",
<%   end -%>
    }, d, config); err != nil {
      return nil, err
    }

    // Replace import id for the resource id
    id, err := replaceVars(d, config, "<%= id_format(object) -%>")
    if err != nil {
        return nil, fmt.Errorf("Error constructing id: %s", err)
    }
    d.SetId(id)

<%-  unless object.virtual_fields.empty? -%>
    // Explicitly set virtual fields to default values on import
  <%-  object.virtual_fields.each do |field| -%>
    if err := d.Set("<%= field.name %>", <%= go_literal(field.default_value) -%>); err != nil {
    	return nil, fmt.Errorf("Error setting <%= field.name %>: %s", err)
    }
  <%   end -%>
<%   end -%>
<%= lines(compile(pwd + '/' + object.custom_code.post_import)) if object.custom_code.post_import -%>

    return []*schema.ResourceData{d}, nil
<% end -%>
}
<% end -%>

<%- nested_prefix = object.nested_query ? "Nested" : "" -%>
<% object.gettable_properties.reject(&:ignore_read).each do |prop| -%>
<%= lines(build_flatten_method(nested_prefix+resource_name, prop, object, pwd), 1) -%>
<% end -%>

<% object.settable_properties.each do |prop| -%>
<%= lines(build_expand_method(nested_prefix+resource_name, prop, object, pwd), 1) -%>
<% end -%>

<% if object.custom_code.encoder -%>
func resource<%= resource_name -%>Encoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) {
<%= lines(compile(pwd + '/' + object.custom_code.encoder)) -%>
}
<% end -%>

<% if object.custom_code.update_encoder-%>
func resource<%= resource_name -%>UpdateEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) {
<%= lines(compile(pwd + '/' + object.custom_code.update_encoder)) -%>
}
<% end -%>

<%  if object.nested_query -%>
<%= compile_template(pwd + '/templates/terraform/nested_query.go.erb',
                 object: object,
                 settable_properties: object.settable_properties,
                 resource_name: resource_name) -%>
<% end -%>

<% if object.custom_code.decoder -%>
func resource<%= resource_name -%>Decoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) {
<%= lines(compile(pwd + '/' + object.custom_code.decoder)) -%>
}
<% end -%>

<% if object.custom_code.post_create_failure -%>
func resource<%= resource_name -%>PostCreateFailure(d *schema.ResourceData, meta interface{}) {
<%= lines(compile(pwd + '/' + object.custom_code.post_create_failure)) -%>
}
<% end -%>

<% if object.schema_version -%>
<%= lines(compile(pwd + "/templates/terraform/state_migrations/#{product_ns.underscore}_#{object.name.underscore}.go.erb")) -%>
<%  end -%>

